/*
Copyright 2021 LuJun

Permission is hereby granted, free of charge, to any person obtaining a copy 
of this software and associated documentation files (the "Software"), to deal 
in the Software without restriction, including without limitation the rights 
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
copies of the Software, and to permit persons to whom the Software is furnished 
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in 
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

import { _decorator, Label, RichText, Sprite, TTFFont, SpriteFrame, log, error } from 'cc';
const { ccclass, property } = _decorator;
import {
    conflict,
    DEBUG_LANGUAGE,
    event,
    EVENT_INDEXES_LOAD,
    EVENT_LANGUAGE_SWITCH,
    INDEXPATH_FONT,
    INDEXPATH_LANGUAGE,
    readJsonConfig,
    request,
    variable
} from '../core';
import { js, path } from '../core/utils';
import { indexMgr } from '../index/index';

const TAG = 'lcc/language/language-manager.ts';

/**
 * @zh
 * 默认语言
 */
export let DEFAULT_LANGUAGE = 'zh';

/**
 * @zh
 * 语言配置
 */
interface LanguageConfig {
    /**
     * @zh
     * 文本配置表
     */
    texts: {
        [key: string]: {
            /**
             * @zh
             * 文本
             */
            text: string;

            /**
             * @zh
             * 使用的字体
             */
            fontName?: string;

            /**
             * @zh
             * 字体大小
             */
            fontSize?: number;
        };
    };

    /**
     * @zh
     * 语言图片表
     */
    images: { [key: string]: string };
}

/**
 * @zh
 * 多语言文本
 */
@ccclass('lcc/language/LanguageText')
export class LanguageText {
    /**
     * @zh
     * 使用富文本
     */
    @property({
        tooltip: '使用富文本'
    })
    useRichText: boolean = false;

    /**
     * @zh
     * 关联标签
     */
    @property({
        type: Label,
        visible() {
            // @ts-ignore
            return this.useRichText === false;
        }
    })
    label: Label = null!;

    /**
     * @zh
     * 关联富文本
     */
    @property({
        type: RichText,
        visible() {
            // @ts-ignore
            return this.useRichText === true;
        }
    })
    richText: RichText = null!;

    /**
     * @zh
     * 关联键
     */
    @property({
        tooltip: '关联键'
    })
    key: string = '';

    /**
     * @zh
     * 关联文本参数
     */
    @property({
        tooltip: '关联文本参数'
    })
    args: string = '';
}

/**
 * @zh
 * 多语言图片
 */
@ccclass('lcc/language/LanguageImage')
export class LanguageImage {
    /**
     * @zh
     * 关联精灵
     */
    @property(Sprite)
    image: Sprite = null!;

    /**
     * @zh
     * 关联键
     */
    @property({
        tooltip: '关联键'
    })
    key: string = '';
}

/**
 * @zh
 * 语言管理器<br/>
 * 管理多语言与其字体
 */
class LanguageManager {
    /**
     * @zh
     * 静态对象
     */
    private static _instance: LanguageManager = null!;

    /**
     * @zh
     * 获得单例对象
     */
    static get instance() {
        if (LanguageManager._instance === null) {
            LanguageManager._instance = new LanguageManager();
        }
        return LanguageManager._instance;
    }

    /**
     * @zh
     * 当前语言
     */
    private _language: string = null!;

    /**
     * @zh
     * 获得当前语言
     */
    get language() {
        return this._language;
    }

    /**
     * @zh
     * 获得语言列表
     */
    get languageList() {
        return Object.keys(indexMgr.getIndex(INDEXPATH_LANGUAGE));
    }

    /**
     * @zh
     * 字体缓冲
     */
    private _fontBuff: { [key: string]: TTFFont } = Object.create(null);

    /**
     * @zh
     * 图片缓冲
     */
    private _imageBuff: { [key: string]: SpriteFrame } = Object.create(null);

    /**
     * @zh
     * 语言配置表
     */
    private _langConfig: LanguageConfig = { texts: Object.create(null), images: Object.create(null) };

    /**
     * @zh
     * 多语言组映射表
     */
    private _langGroupMap: { [key: string]: { texts: LanguageText[]; images: LanguageImage[] } } = Object.create(null);

    /**
     * @zh
     * 构造管理器
     */
    private constructor() {
        event.on(`${EVENT_INDEXES_LOAD}${INDEXPATH_LANGUAGE}`, this.onLanguageIndexesLoaded, this);
    }

    /**
     * @zh
     * 当索引加载
     *
     * @param ivalue 索引路径对象
     */
    private async onLanguageIndexesLoaded(ivalue: { [key: string]: any }) {
        this._language = this._language || DEFAULT_LANGUAGE;
        if (ivalue) {
            let tfiles = ivalue[this._language];
            if (tfiles) {
                for (let tfile of tfiles) {
                    let table = await readJsonConfig(tfile);
                    if (!table) {
                        error(TAG, `language file ${tfile} read fail !!!`);
                    } else {
                        table = variable.formatJSON(table, { localpath: path.dirname(tfile) });
                        this.loadTable(table, tfile);
                    }
                }
            }
        }
    }

    /**
     * @internal
     */
    private async reloadNowLanguage() {
        this._langConfig = { texts: Object.create(null), images: Object.create(null) };
        await this.loadPreLanguages();
        await this.onLanguageIndexesLoaded(indexMgr.getIndex(INDEXPATH_LANGUAGE));
        this.removeInvalidBuff();
    }

    /**
     * @zh
     * 移除无效的缓冲
     */
    removeInvalidBuff() {
        // 字体
        let fontconfs = indexMgr.getIndex(INDEXPATH_FONT);
        for (let fname of Object.keys(this._fontBuff)) {
            let font = this._fontBuff[fname];
            if (!fontconfs[fname] && font) {
                font.decRef();
                delete this._fontBuff[fname];
            }
        }

        // 图片
        for (let ipath of Object.keys(this._imageBuff)) {
            let image = this._imageBuff[ipath];
            let find = false;
            (() => {
                for (let k in this._langConfig.images) {
                    let path = this._langConfig.images[k];
                    if (ipath === path) {
                        find = true;
                        return;
                    }
                }
            })();
            if (!find) {
                image.decRef();
                delete this._imageBuff[ipath];
            }
        }
    }

    /**
     * @zh
     * 合并表<br/>
     * 合并规则与[[js.mergeMap]]一致
     *
     * @param table - 新表对象
     * @param tfile - 文件路径
     */
    private loadTable(table: Object, tfile: string = '[unknown]') {
        js.mergeMap(this._langConfig, table, { source: tfile });
        DEBUG_LANGUAGE && log(TAG, `load language table ${JSON.stringify(table)} from ${tfile}`);
    }

    /**
     * @zh
     * 加载前置配置
     */
    async loadPreLanguages() {
        this._language = this._language || DEFAULT_LANGUAGE;
        let preLanguageFile = 'resources://lcc-assets/pre-languages.json';
        let preLanguages = await request.get(preLanguageFile);
        let languageConfig = preLanguages?.[this._language];
        if (languageConfig) {
            this.loadTable(variable.formatJSON(languageConfig), preLanguageFile);
        }
    }

    /**
     * @zh
     * 设置并更新语言
     *
     * @param language - 语言名称
     */
    async setLanguage(language: string) {
        if (this._language !== language) {
            this._language = language;
            await this.reloadNowLanguage();
            await this.updateAllLangGroup();
            try {
                event.emit(EVENT_LANGUAGE_SWITCH + language);
                event.emit(EVENT_LANGUAGE_SWITCH, language);
            } catch (e) {
                error(TAG, `setLanguage Exception ${e}`);
            }
        }
    }

    /**
     * @zh
     * 更新所有语言组
     */
    private async updateAllLangGroup() {
        for (let group in this._langGroupMap) {
            await this.updateLangGroup(group);
        }
    }

    /**
     * @zh
     * 更新指定语言组
     *
     * @param group - 组名称
     */
    private async updateLangGroup(group: string) {
        if (this._language) {
            let groupconf = this._langGroupMap[group];
            if (groupconf) {
                if (groupconf.texts) {
                    for (let tc of groupconf.texts) {
                        await this.updateText(tc.useRichText ? tc.richText : tc.label, tc.key, tc.args.split(','));
                    }
                }
                if (groupconf.images) {
                    for (let ic of groupconf.images) {
                        await this.updateImage(ic.image, ic.key);
                    }
                }
            }
        }
    }

    /**
     * @zh
     * 更新文本
     *
     * @param target - 目标对象
     * @param key - 关联文本key
     * @param args - 文本参数
     */
    async updateText(target: Label | RichText, key: string, ...args: any[]) {
        let textConf = this._langConfig.texts[key];
        if (textConf) {
            target.string = variable.formatString(textConf.text, args);
            if (textConf.fontSize) {
                target.fontSize = textConf.fontSize;
            }
            if (textConf.fontName) {
                let font = await this.getFont(textConf.fontName);
                if (font && target.isValid) {
                    target.font = font;
                }
            }
        } else {
            error(TAG, `updateText language:${this._language} key:${key} not found!`);
        }
    }

    /**
     * @zh
     * 获得字体
     *
     * @param fname - 字体名称
     */
    private async getFont(fname: string) {
        let font = this._fontBuff[fname];
        if (!font) {
            let path = indexMgr.getIndex<string>(INDEXPATH_FONT + '/' + fname);
            font = await request.get<TTFFont>(path, { subproto: 'ttffont' });
            if (font) {
                font.addRef();
                this._fontBuff[fname] = font;
            }
        }
        return font;
    }

    /**
     * @zh
     * 更新图片
     *
     * @param target - 目标对象
     * @param key - 关联图片key
     */
    async updateImage(target: Sprite, key: string) {
        let imgPath = this._langConfig.images[key];
        if (imgPath) {
            let spframe = await this.getLanguageImage(imgPath);
            if (spframe && target.isValid) {
                target.spriteFrame = spframe;
            }
        } else {
            error(TAG, `updateImage language:${this._language} key:${key} not found!`);
        }
    }

    /**
     * @zh
     * 获得语言图片
     *
     * @param fpath - 语言图片路径
     */
    private async getLanguageImage(fpath: string) {
        let spframe = this._imageBuff[fpath];
        if (!spframe) {
            spframe = await request.get<SpriteFrame>(fpath, { subproto: 'spriteframe' });
            if (spframe) {
                spframe.addRef();
                this._imageBuff[fpath] = spframe;
            }
        }
        return spframe;
    }

    /**
     * @zh
     * 添加语言组<br/>
     * 配套组件使用
     *
     * @param group - 组名称
     * @param texts - 文本组
     * @param images - 图片组
     */
    async addLanguageGroup(group: string, texts?: LanguageText[], images?: LanguageImage[]) {
        if (this._langGroupMap[group]) {
            conflict(TAG, `language group conflict ${group}!!!`);
        } else {
            this._langGroupMap[group] = {
                texts: texts!,
                images: images!
            };
            await this.updateLangGroup(group);
        }
    }

    /**
     * @zh
     * 移除语言组<br/>
     * 配套组件使用
     *
     * @param group - 组名称
     */
    removeLanguageGroup(group: string) {
        delete this._langGroupMap[group];
    }

    /**
     * @zh
     * 获得当前语言文本
     *
     * @param key - 关联key
     * @param args - 参数数组
     */
    getText(key: string, ...args: any[]) {
        let textConf = this._langConfig.texts[key];
        if (textConf) {
            return variable.formatString(textConf.text, ...args);
        }
    }

    /**
     * @zh
     * 获得当前语言图片
     *
     * @param key - 关联key
     */
    async getImage(key: string) {
        let imgPath = this._langConfig.images[key];
        if (imgPath) {
            return await this.getLanguageImage(imgPath);
        }
    }
}

/**
 * @zh
 * 语言管理器对象
 */
export const languageMgr = LanguageManager.instance;

variable.set('LANGUAGE', (key: string) => {
    return languageMgr.getText(key);
});
